using System;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using Clap.Core.ObjectModel;
using EnvRide.Core.ObjectModel;
using Environment = System.Environment;
using System.Reflection;
using System.Collections.Generic;
using log4net.Config;
using CodeMadeSimple.PropertiesManagement;

namespace EnvRide.CommandLine
{
    public class Program
    {
        #region Methods

        public static bool DoMain(string[] args)
        {
            bool succeded = false;

            var clap = new ClapDescriptor();
            clap.SwitchArgumentsIdentifier = '=';
            clap.SwitchArgumentsSeparator = ',';
            clap.SwitchMatchCaseSensitive = false;

            clap.Name = "EnvRide CommandLine Tool";
            clap.Description = "Provides a way to process environments from the command line.";

            var envRideProjArg = new ArgumentInfo("ProjectFile", true, "Path to the EnvRide project you wish to process.", null);
            clap.Arguments.Add(envRideProjArg);

            var envSwt = new SwitchInfo("Environment", "env", false, "Specifies the environment to process.");
            envSwt.CanBeSuppliedMoreThanOnce = true;
            var envArg = new ArgumentInfo("EnvironmentName", true, "Name of the environment to process.", null, false);
            var envOutputFilePathArg = new ArgumentInfo("OutputFilePath", false,
                                                        "Path to the output file generated from the merge process. If ommited the OutputFilePath property from the environment its used.",
                                                        null, false);
            envSwt.Arguments.Add(envArg);
            envSwt.Arguments.Add(envOutputFilePathArg);
            clap.Switches.Add(envSwt);

            var configFileSwt = new SwitchInfo("ConfigFile", "conf", false, "Specifies the config file to process.");
            configFileSwt.CanBeSuppliedMoreThanOnce = false;
            var configFileArg = new ArgumentInfo("ConfigFileName", true, "Path to the config file you wish to process.", null, false);
            configFileSwt.Arguments.Add(configFileArg);
            clap.Switches.Add(configFileSwt);

            var defaultEnvSwt = new SwitchInfo("DefaultEnvironment", "defEnv", false, "Specifies the default environment to be used.");
            defaultEnvSwt.CanBeSuppliedMoreThanOnce = false;
            var defaultEnvSwtArg = new ArgumentInfo("DefaultEnvironmentName", true, "Specifies the default environment name.");
            defaultEnvSwt.Arguments.Add(defaultEnvSwtArg);
            clap.Switches.Add(defaultEnvSwt);

            var encodingSwt = new SwitchInfo("Encoding", "enc", false, "Specifies the encoding to use.");
            encodingSwt.CanBeSuppliedMoreThanOnce = false;
            var encodingArg = new ArgumentInfo("EncodingName", true, "Name of the encoding to use.");
            encodingSwt.Arguments.Add(encodingArg);
            clap.Switches.Add(encodingSwt);

            var propertiesSwt = new SwitchInfo("Property", "p", false, "Specifies a property.");
            propertiesSwt.CanBeSuppliedMoreThanOnce = true;
            propertiesSwt.ArgumentsIdentifier = ':';
            propertiesSwt.ArgumentsSeparator = '=';
            var propNameArg = new ArgumentInfo("PropertyName", true, "Name of the property.");
            propertiesSwt.Arguments.Add(propNameArg);
            var propValueArg = new ArgumentInfo("PropertyValue", true, "Value of the property.");
            propertiesSwt.Arguments.Add(propValueArg);

            clap.Switches.Add(propertiesSwt);

            var propertiesFilesSwt = new SwitchInfo("PropertiesFiles", "propFiles", false, "Specifies a list of properties files to include.");
            propertiesFilesSwt.CanBeSuppliedMoreThanOnce = false;
            var propertiesFileArg = new ArgumentInfo("Path", true, "Comma separated paths.");
            propertiesFilesSwt.Arguments.Add(propertiesFileArg);

            clap.Switches.Add(propertiesFilesSwt);

            ClapDescriptorParseOperationResult result = clap.Parse(args);

            if (result.Result.Switches.WasSwitchSupplied(clap.HelpSwitch))
            {
                Console.WriteLine(clap.GetHelpText());

                succeded = true;
            }
            else if (!result.HasErrors)
            {
                Process(envRideProjArg, envSwt, configFileSwt, defaultEnvSwt, propertiesSwt, propertiesFilesSwt, result);

                succeded = true;
            }
            else
            {
                Console.WriteLine("Errors were found! (Try using the /{0} switch):", clap.HelpSwitch.Name);

                foreach (ClapParseMessage msg in result.Errors)
                {
                    Console.WriteLine("- {0}: {1}", msg.ErrorType, msg.Text);
                }
            }

            return succeded;
        }

        [STAThread]
        public static void Main(string[] args)
        {
            Console.WriteLine("*** ENVRIDE ***");

            Console.WriteLine("Processing args:");

            foreach (var arg in args)
            {
                Console.WriteLine("- {0}", arg);
            }

            try
            {
                if (DoMain(args))
                {
                    Environment.Exit(0);
                }
                else
                    Environment.Exit(1);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine("Errors occurred!");

                Console.Error.WriteLine(ex.Message);
                Console.Error.WriteLine(ex.StackTrace);
                Environment.Exit(1);
            }
        }

        private static void Process(ArgumentInfo envRideProjArg, SwitchInfo envSwt, SwitchInfo configFileSwt, SwitchInfo defaultEnvSwt, SwitchInfo propertiesSwt, SwitchInfo propertiesFilesSwt, ClapDescriptorParseOperationResult result)
        {
            ArgumentCollection envRideProjSuppliedArgs = result.Result.Arguments.GetSuppliedArguments(envRideProjArg);

            // Retrieves Project files
            foreach (Argument envRideProjSuppliedArg in envRideProjSuppliedArgs)
            {
                string projFilePath = envRideProjSuppliedArg.Value;

                EnvRideProject proj = EnvRideProject.Deserialize(projFilePath);

                if (result.Result.Switches.WasSwitchSupplied(configFileSwt))
                {
                    Switch configFileSuppliedSwt = result.Result.Switches.GetSuppliedSwitches(configFileSwt).SingleOrDefault();

                    proj.ConfigFilePath = configFileSuppliedSwt.Arguments[0].Value;
                }

                if (result.Result.Switches.WasSwitchSupplied(defaultEnvSwt))
                {
                    Switch defaultEnvSuppliedSwt = result.Result.Switches.GetSuppliedSwitches(defaultEnvSwt).SingleOrDefault();

                    proj.DefaultEnvironmentName = defaultEnvSuppliedSwt.Arguments[0].Value;
                }

                EnvRideProcessArguments args = new EnvRideProcessArguments();
                PropertyContext propCtx = new PropertyContext("EnvRide");
                args.PropertyContainer = propCtx;
                args.PropertyNotFoundAction = CodeMadeSimple.PropertiesManagement.PropertyNotFoundAction.ThrowError;

                if (result.Result.Switches.WasSwitchSupplied(propertiesSwt))
                {
                    SwitchCollection switches = result.Result.Switches.GetSuppliedSwitches(propertiesSwt);

                    foreach (Switch prop in switches)
                    {
                        String[] names = prop.Arguments[0].Value.Split('.');

                        PropertiesHolder currentProp = propCtx;

                        for (int i = 0; i < names.Length; i++)
                        {
                            String currentName = names[i];

                            if (currentProp.HasProperty(currentName))
                            {
                                currentProp = currentProp.GetProperty(currentName);
                            }
                            else
                            {
                                Property newProp = new Property { Name = currentName };
                                currentProp.AddProperty(newProp);
                                currentProp = newProp;
                            }
                        }

                        ((Property)currentProp).Value = prop.Arguments[1].Value;
                    }
                }

                if (result.Result.Switches.WasSwitchSupplied(envSwt))
                {
                    // Process the supplied environments
                    SwitchCollection envSuppliedSwts = result.Result.Switches.GetSuppliedSwitches(envSwt);

                    foreach (Switch envSuppliedSwt in envSuppliedSwts)
                    {
                        args.EnvironmentName = envSuppliedSwt.Arguments[0].Value;

                        if (envSuppliedSwt.Arguments.Count > 1)
                            args.OutputFilePath = envSuppliedSwt.Arguments[1].Value;

                        proj.Process(args);
                    }
                }
                else
                {
                    proj.Process(args);
                }
            }

            Console.WriteLine("Merge process completed with success!");
        }

        private static string CleanPath(string path)
        {
            return path.Replace("\\:", ":");
        }

        #endregion
    }
}